package mirrormonkey.util.annotations.hfilter;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.Iterator;

/**
 * <tt>ClassFilter</tt> contains means to define a list of class filters and to
 * evaluate classes against that filter list, checking whether they are included
 * or excluded by the filter list.
 * 
 * The list is parsed in a linear fashion. Every filter in the list may include
 * or exclude all classes, one specific class or every class that is assignment
 * compatible with a specific class (which means that instances need to be
 * cast-able to the class given).
 * 
 * @author Philipp Christian Loewner
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassFilter {

	/**
	 * Used to specify whether a <tt>ClassFilter</tt> matches a single specific
	 * class or every class that is assignment compatible with.
	 */
	public static enum HierarchyType {

		/**
		 * Specifies that the filter matches exactly the classes listed in
		 * <tt>classes</tt>
		 */
		SPECIFIC,

		/**
		 * Specifies that the filter matches every class that is listed in
		 * <tt>classes</tt> and every class that is assignment compatible to at
		 * least one of them
		 */
		HIERARCHY
	}

	/**
	 * Used to specify whether classes that match a <tt>ClassFilter</tt> are
	 * explicitly included or excluded from the list of included classes.
	 */
	public static enum SelectType {

		/**
		 * Specifies that every class that matches the filter should be included
		 * in the list of filtered classes until explicitly excluded by another
		 * filter from the list.
		 */
		INCLUDE,

		/**
		 * Specifies that every class that matches the filter should be excluded
		 * from the list of filtered classes until explicitly included by
		 * another filter in the list.
		 */
		EXCLUDE
	}

	/**
	 * Selects whether this <tt>ClassFilter</tt> is applied to all specified
	 * <tt>classes</tt> or all <tt>classes</tt> and all classes assignment
	 * compatible to them.
	 * 
	 * @return the <tt>HierarchyType</tt> of this <tt>ClassFilter</tt>
	 */
	public HierarchyType hierarchy() default HierarchyType.SPECIFIC;

	/**
	 * Selects whether this <tt>ClassFilter</tt> includes the matching classes
	 * into the list of selected classes or excludes matching classes from the
	 * list.
	 * 
	 * @return the <tt>SelectType</tt> of this <tt>ClassFilter</tt>
	 */
	public SelectType select() default SelectType.INCLUDE;

	/**
	 * Selects every class (or superclass) that this <tt>ClassFilter</tt>
	 * matches
	 * 
	 * @return an array of every class that this <tt>ClassFilter</tt> matches or
	 *         an empty class array if this <tt>ClassFilter</tt> matches every
	 *         class
	 */
	public Class<?>[] classes() default {};

	/**
	 * Utility class to evaluate classes against lists of <tt>ClassFilters</tt>
	 * 
	 * @author Philipp Christian Loewner
	 * 
	 */
	public static final class Eval {

		/**
		 * Private constructor to make it an utility class
		 */
		private Eval() {
		}

		/**
		 * Checks a single class against a list of <tt>ClassFilters</tt>.
		 * Generally, <tt>filters</tt> is iterated and every filter overwrites
		 * settings of previous filters that match the same class.
		 * 
		 * @param filters
		 *            array of <tt>ClassFilters</tt> to check <tt>toTest</tt>
		 *            against
		 * @param toTest
		 *            class to check against the filters
		 * @return <tt>true</tt>, if the last filter in <tt>filters</tt> that
		 *         matches <tt>toTest</tt> is set to <tt>INCLUDE</tt>,
		 *         <tt>false</tt> otherwise or if no <tt>ClassFilter</tt>
		 *         matches <tt>toTest</tt>
		 */
		public static final boolean contains(ClassFilter[] filters,
				Class<?> toTest) {
			boolean state = false;
			for (ClassFilter f : filters) {
				final boolean specific = f.hierarchy().equals(
						HierarchyType.SPECIFIC);
				boolean selected = f.classes().length == 0;
				if (!selected) {
					ClassSelection: for (Class<?> c : f.classes()) {
						if ((specific && c.equals(toTest))
								|| (!specific && c.isAssignableFrom(toTest))) {
							selected = true;
							break ClassSelection;
						}
					}
				}
				if (selected) {
					state = f.select().equals(SelectType.INCLUDE);
				}
			}
			return state;
		}

		public static final void filter(ClassFilter[] filters,
				Collection<Class<?>> toFilter) {
			for (Iterator<Class<?>> i = toFilter.iterator(); i.hasNext();) {
				if (!contains(filters, i.next())) {
					i.remove();
				}
			}
		}
	}

}
